package jmine.tec.script.persistent.validators;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.script.persistent.ScriptPersistenceMessages;
import jmine.tec.script.persistent.domain.Script;
import jmine.tec.script.persistent.domain.ScriptDependency;
import jmine.tec.script.persistent.domain.enumx.BaseScriptPurposeIdentifier;
import bancosys.tec.exception.LocalizedMessageHolder;
import bancosys.tec.persist.validator.AbstractValidator;
import bancosys.tec.persist.validator.ValidationError;

/**
 * Validador de script.
 * 
 * @author lundberg
 */
@Unmodifiable
public class ScriptValidator extends AbstractValidator<Script> {

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ValidationError> validateInsert(Script bean) {
        return this.customValidate(bean);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ValidationError> validateUpdate(Script bean) {
        return this.customValidate(bean);
    }

    /**
     * Valida a consistência do script, uso e api's.
     * 
     * @param bean bean
     * @return List de ValidationError.
     */
    private List<ValidationError> customValidate(Script bean) {
        List<ValidationError> errors = null;
        this.validateAPIs(bean, errors);
        this.validateDependencies(bean, errors);
        return errors;
    }

    /**
     * Valida se o script está escrito na mesma linguagem que suas API's.
     * 
     * @param bean bean
     * @param errors errors
     */
    private void validateAPIs(Script bean, List<ValidationError> errors) {
        for (Script api : bean.getApis()) {
            if (bean.getLanguage() != null && !api.getLanguage().equals(bean.getLanguage())) {
                this.addError(errors, this.createError(ScriptPersistenceMessages.VALIDATION_DIFFERENT_LANGUAGE_API.create(
                        bean.getMnemonico(), bean.getLanguage().getMnemonico(), api.getLanguage().getMnemonico())));
            }
            if (!api.getPurpose().isIdentifiedBy(BaseScriptPurposeIdentifier.API)) {
                this.addError(
                        errors,
                        this.createError(ScriptPersistenceMessages.VALIDATION_SCRIPT_IS_NOT_API.create(bean.getMnemonico(),
                                api.getMnemonico())));
            }
        }
    }

    /**
     * Valida que o script não possui inconsistências em suas dependências, como o mesmo bean sendo injetado em 2 variáveis ou 2 beans sendo
     * injetados na mesma.
     * 
     * @param script script
     * @param errors errors
     */
    private void validateDependencies(Script script, List<ValidationError> errors) {
        Map<String, Set<ScriptDependency>> beans = new HashMap<String, Set<ScriptDependency>>();
        Map<String, Set<ScriptDependency>> variables = new HashMap<String, Set<ScriptDependency>>();
        this.readDependencies(script, beans, variables, new HashSet<Script>());
        for (Map.Entry<String, Set<ScriptDependency>> e : beans.entrySet()) {
            if (e.getValue().size() > 1) {
                this.addError(
                        errors,
                        this.createError(ScriptPersistenceMessages.VALIDATION_BEAN_ADDED_MULTIPLE_TIMES.create(script.getMnemonico(),
                                e.getKey(), e.getValue().size())));
            }
        }
        for (Map.Entry<String, Set<ScriptDependency>> e : variables.entrySet()) {
            if (e.getValue().size() > 1) {
                this.addError(errors, this.createError(ScriptPersistenceMessages.VALIDATION_VARIABLE_USED_MULTIPLE_TIMES.create(
                        script.getMnemonico(), e.getValue().size(), e.getKey())));
            }
        }
    }

    /**
     * Lê as dependências dos scripts e suas API's e preenche os mapas de nome de bean para scriptDependency e
     * 
     * @param script script
     * @param beans beans
     * @param variables variables
     * @param processed processed
     */
    private void readDependencies(Script script, Map<String, Set<ScriptDependency>> beans, Map<String, Set<ScriptDependency>> variables,
            Set<Script> processed) {
        if (processed.contains(script)) {
            return;
        }
        processed.add(script);
        for (ScriptDependency dep : script.getDependencies()) {
            this.addDependency(beans, dep.getBean(), dep);
            this.addDependency(variables, dep.getVariable(), dep);
        }
        for (Script api : script.getApis()) {
            this.readDependencies(api, beans, variables, processed);
        }
    }

    /**
     * Adiciona a dependência o mapa com a chave
     * 
     * @param depMap depMap
     * @param key key
     * @param dep dep
     */
    private void addDependency(Map<String, Set<ScriptDependency>> depMap, String key, ScriptDependency dep) {
        if (!depMap.containsKey(key)) {
            depMap.put(key, new HashSet<ScriptDependency>());
        }
        depMap.get(key).add(dep);
    }

    /**
     * Cria um novo ValidationError
     * 
     * @param message message
     * @return ValidationError
     */
    private ValidationError createError(LocalizedMessageHolder message) {
        return new ValidationError(message);
    }
}
